package it.geosolutions.inversecolormap;

import java.util.Arrays;

public final class ApproximateInverseColorMapComputation {
	public final static class IndexedRGBInterleavedColor implements Comparable {
		/**
		 * Converts a single rgb color into a long interleaved value.
		 * 
		 * @param reds
		 *            red component
		 * @param greens
		 *            green component
		 * @param blues
		 *            blue componenet
		 * @return the a long interleaved rgb value value
		 */
		public final static long getInterleavedValue(byte r, byte g, byte b) {
			long val = 0;
			for (int i = 0; i < 8; i++) {
				if ((b & (1 << i)) > 0)
					val |= (1 << i * 3);
				if ((g & (1 << i)) > 0)
					val |= (1 << i * 3 + 1);
				if ((r & (1 << i)) > 0)
					val |= (1 << i * 3 + 2);

			}
			return val;
		}

		private byte r;

		private byte g;

		private byte b;

		private int index;

		private long interleavedVal;

		public IndexedRGBInterleavedColor(byte r, byte g, byte b, int index) {
			this.r = r;
			this.g = g;
			this.b = b;
			this.index = index;
			interleavedVal = getInterleavedValue(r, g, b);

		}

		public int compareTo(Object that) {
			if (that instanceof IndexedRGBInterleavedColor) {
				final IndexedRGBInterleavedColor thatRGB = (IndexedRGBInterleavedColor) that;
				if (this.interleavedVal < thatRGB.interleavedVal)
					return -1;
				else if (this.interleavedVal == thatRGB.interleavedVal)
					return 0;
				return 1;

			}
			throw new IllegalArgumentException();
		}

		public byte getB() {
			return b;
		}

		public void setB(byte b) {
			this.b = b;
		}

		public byte getG() {
			return g;
		}

		public void setG(byte g) {
			this.g = g;
		}

		public int getIndex() {
			return index;
		}

		public void setIndex(int index) {
			this.index = index;
		}

		public byte getR() {
			return r;
		}

		public void setR(byte r) {
			this.r = r;
		}

		public long getInterleavedVal() {
			return interleavedVal;
		}

	}

	private IndexedRGBInterleavedColor[] interleavedColorMap;

	private byte[] reds;

	private byte[] greens;

	private byte[] blues;

	/**
	 * constructor for an {@link EfficientInverseColorMapComputation} class.
	 * 
	 * <p>
	 * This constructor builds up an {@link EfficientInverseColorMapComputation}
	 * by using the reds greens and blues components of the color map we want to use.
	 * Transparency colo must be taken into account at an higher level.
	 * 
	 * @param reds
	 * @param green
	 * @param blue
	 * @param orderedInterleavedColorMap
	 */
	public ApproximateInverseColorMapComputation(byte[] reds, byte[] green,
			byte[] blue,
			final IndexedRGBInterleavedColor[] orderedInterleavedColorMap) {

		this.reds = reds;
		this.greens = green;
		this.blues = blue;
		if (interleavedColorMap == null) {
			this.interleavedColorMap = new IndexedRGBInterleavedColor[reds.length];
			for (int i = 0; i < reds.length; i++) {
				interleavedColorMap[i] = new IndexedRGBInterleavedColor(
						reds[i], green[i], blue[i], i);
			}
			Arrays.sort(interleavedColorMap);

		} else
			this.interleavedColorMap = orderedInterleavedColorMap;
		assert this.reds.length == this.interleavedColorMap.length;

	}

	public ApproximateInverseColorMapComputation(byte[] r, byte[] g, byte[] b) {
		this(r, g, b, null);
	}

	public final int getIndexNearest(byte red, byte green, byte blue) {
		final IndexedRGBInterleavedColor color = new IndexedRGBInterleavedColor(
				red, green, blue, 0);
		int index = Arrays.binarySearch(this.interleavedColorMap, color);
		if (index < 0)
			return -(index + 1);
		if (index >= interleavedColorMap.length)
			index = interleavedColorMap.length - 1;
		return interleavedColorMap[index].getIndex();
	}

}
